Skip to content
created by Aha00aAha00a at 2026-05-26
last modified by Aha00aAha00a at 2026-09-10
revision: 3

Dev Admin UI 권한별 메뉴

전체Admin과 SiteAdmin의 권한에 따라 /Admin 네비게이션 메뉴를 다르게 표시한다.

1. 네비게이션 구조

[전체 관리]        ← 전체Admin만 표시. Sites 블록보다 위에 온다
  Dashboard
  User
  RecentChanges
  AccessLog
  Crawler Cache
  S3 Browser
  API Keys
  Nickname Requests

[Sites]
  ▼ AhaWiki (#1)   ← 현재 사이트(request.host): 자동 펼침. 강조(filled)는 URL 의 site seq 를 따른다
      Config
      Page
      Cache
      Permission
      AccessLog    ← 전체Admin만 표시
      SiteAdmins   ← 전체Admin만 표시
  ▶ OtherSite (#2) ← 전체Admin만 표시 (접힌 상태)

이 그림은 app/assets/js/admin/navigation.jsx 를 옮긴 것이다. 항목을 더하거나 이름을 바꾸면 여기도 바꾼다 — 2026-05-24 이후 넉 달 동안 이 그림은 없어진 "Meta목록" 을 그리고 있었고, 나중에 붙은 API Keys·Nickname Requests 는 없었다.

  • 현재 사이트는 request.host로 판별 (/api/me의 currentSiteSeq 활용)
  • SiteAdmin에게는 담당 사이트만 표시, 전체Admin은 전체 표시

2. 구현 결과

2.1. Backend

  • GET /api/me 추가: loggedIn, seq, nickname, loginEmail, profileImageUrl, isAdmin, siteAdminSeqs, currentSiteSeq 반환. 로그아웃 상태면 {"loggedIn": false} 만 돌려주고, 내비게이션은 loggedIn 이 참일 때만 site 목록을 가져온다
  • SiteAdmin.selectByUser(userSeq) 추가
  • /api/Admin/Sites: SiteAdmin은 담당 사이트만 반환하도록 수정
  • Admin.scala: isAdmin || isSiteAdmin 기준으로 접근 허용

2.2. Frontend

  • AdminLayout에서 me fetch 후 Navigation에는 prop으로, 각 페이지에는 AdminContext.Provider로 전달
  • SiteNavItem 컴포넌트 분리로 사이트별 서브메뉴 재사용
  • me.isAdmin이 false 이면 Dashboard 는 담당 사이트 목록만 보여준다. 전체Admin 전용 페이지는 프론트에서 막지 않는다 — API 의 403 이 HTTP 403 오류 문구로 보이거나(S3 Browser·API Keys·Nickname Requests) 빈 화면으로 남는다(User·RecentChanges·AccessLog·Crawler Cache). 2026-05-24 에 넣었던 접근 불가 메시지는 같은 날 뺐다
  • Me API 로딩 중 네비게이션 skeleton 표시

3. 설계 결정

실제 보안은 API 레벨(isAdmin / isSiteAdmin)에서 처리한다. 프론트 메뉴 분기는 UX 목적으로만 사용한다.

4. See Also

4.2. Similar Pages

Similar pages by cosine similarity. Words after page name are term frequency.

  • 70.86% Dev SiteAdmin site(14:27), admin(14:25), api(8:5), seq(5:6), user(3:5), 사이트(2:3), 담당(3:1), 전체(2:2), access(2:1), admins(1:2)
  • 52.90% Dev ApiResponse site(14:36), api(8:42), admin(14:33), seq(5:9), 403(2:9), access(2:9), get(1:10), host(1:9), crawler(1:8), page(1:8)
  • 50.14% Dev ApiControllers api(8:24), admin(14:12), site(14:9), seq(5:3), cache(3:3), user(3:3), url(2:4), 사이트(2:4), wiki(1:5), nickname(2:2)
  • 39.89% Dev SisterWiki site(14:51), admin(14:1), page(1:14), wiki(1:12), 현재(2:9), api(8:1), cache(3:6), 같은(1:8), 표시한다(1:7), seq(5:2)
  • 34.09% Dev Cache cache(3:60), site(14:35), aha(1:36), wiki(1:36), seq(5:18), admin(14:8), page(1:21), api(8:12), 사이트(2:10), app(1:9)
  • 32.91% Dev Api api(8:147), user(3:44), page(1:34), admin(14:20), wiki(1:22), aha(1:19), 않는다(1:16), get(1:14), 표시(7:6), seq(5:8)
  • 32.57% UserNicknameChange user(3:33), nickname(2:34), api(8:23), admin(14:13), site(14:1), 않는다(1:14), seq(5:9), requests(3:10), 같은(1:8), 표시(7:1)

4.3. Adjacent Pages

Control
≤ 32
all
1.0x
1.0x
80
-120
ON
Metrics
Nodes(visible/total)0/0
Links(visible/total)0/0
Avg degree0.00
Depth coverage0
Queue(fetch/graph)0 / 0
Zoom(scale)1.00x
Ctrl/⌘ + Scroll: Zoom
Root 1-hop 2-hop+